package controller.commands;

import controller.Constants;
import core.BoxNode;
import core.InPort;
import core.Node;
import core.OutPort;
import core.Port;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import model.graphic.locations.AbsoluteLocation;
import model.graphic.locations.Location;
import model.graphic.objects.GBoxNode;
import model.graphic.objects.GConnection;
import model.graphic.objects.GDispatcherPort;
import model.graphic.objects.GHandleConnection;
import model.graphic.objects.GInputPort;
import model.graphic.objects.GNode;
import model.graphic.objects.GOutput;
import model.graphic.objects.GResizeHandle;
import model.graphic.objects.GTransmitterNode;
import model.graphic.objects.GTrigger;
import model.graphic.objects.GTriggerConnection;
import model.graphic.objects.Graphical;
import model.graphic.objects.RectangleAttachment;
import model.graphic.objects.RectangleGraphical;
import view.GUI.LabelReader;
import view.GUI.sidepanels.Palette;
import view.GUI.sidepanels.PaletteItem;
import view.GUI.sidepanels.TransferableArrayList;

/**
 *
 * @author Ben
 */
public class AddBoxNodeCommand  extends BasicCommand{
    private String name;
    private String info;
    private Palette palette;

    public AddBoxNodeCommand(Palette palette, String name, String info) {
        this.palette = palette;
        this.name = name;
        this.info = info;
    }

    @Override
    public void execute() {
        //grootte bepalen van boxnode
        int minx = 1000, miny = 1000;
        int maxx = 0, maxy = 0;
        for (Graphical g : gmodel.getSelection(Constants.getNodeString())) {
            if (!GBoxNode.class.isAssignableFrom(g.getClass()) && !((GNode) g).isBoxed()) {
                RectangleGraphical rg = (RectangleGraphical) g;
                Location loc1 = rg.getUpperLeft();
                Location loc2 = rg.getLowerRight();
                minx = Math.min(minx, loc1.getX());
                miny = Math.min(miny, loc1.getY());
                maxx = Math.max(maxx, loc2.getX());
                maxy = Math.max(maxy,loc2.getY());
            }
        }

        //nieuwe boxnode aanmaken en toevoegen aan view en model
        GBoxNode gn = new GBoxNode(name, new AbsoluteLocation(minx - 140, miny - 20), maxx-minx, maxy-miny);
        gn.setMinSize(40);
        GResizeHandle rh = new GResizeHandle(gn, 0);
        gn.setResizer(rh);
        gmodel.addObject(gn);
        gview.add(gn, 10);
        gmodel.addObject(rh);
        gview.add(rh);

        BoxNode cb = new BoxNode();
        //add internals to (G)boxnode
        Set<GConnection> connections = new HashSet<GConnection>();
        for (Graphical g : gmodel.getSelection(Constants.getAllString())) {
            if(GBoxNode.class.isAssignableFrom(g.getClass())){
                GBoxNode gb = ((GBoxNode)g);
                gb.setBoxNode(gn);
                gb.setBoxed(true);
                gn.addInternalObject(g);

                //collect all connections, will be needed later
                ArrayList<Graphical> tms = gb.getTransmitterObjects();
                for(Graphical tm: tms){
                    if(GTransmitterNode.class.isAssignableFrom(tm.getClass())){
                        connections.addAll(gmodel.getConnectionFromNode((GTransmitterNode)tm));
                    }
                }
                //add all attachments
                for (Graphical gattach : gmodel.getAttachedAllNode(gb)) {
                    gn.addInternalObject(gattach);
                    //gview.setLayer(gattach, 10, 0);
                    gmodel.removeObject(gattach);
                    gview.remove(gattach);
                }
                //add it to the code
                Node noi = sync.get(gb);
                cb.putNode(noi.getClass().getSimpleName(), noi);

            }else if(GNode.class.isAssignableFrom(g.getClass())){
                GNode node = (GNode)g;
                node.setBoxNode(gn);
                node.setBoxed(true);
                gn.addInternalObject(g);

                //collect all connections, will be needed later
                for(GConnection con : node.getConns()){
                    connections.add(con);
                }
                //add all attachments
                for (Graphical gattach : gmodel.getAttachedAllNode(node)) {
                    gn.addInternalObject(gattach);
                    //gview.setLayer(gattach, 10, 0);
                    gmodel.removeObject(gattach);
                    gview.remove(gattach);
                }
                //add it to the code
                Node noi = sync.get(node);
                cb.putNode(noi.getClass().getSimpleName(), noi);
                
            }
            gmodel.removeObject(g);
            gview.remove(g);
        }

        cmodel.addObject(cb);
        sync.put(gn, cb);

        //add the internal connections and the transmitterports
        gmodel.setSelected(gn);
        //incoming
        HashMap<GTrigger, ArrayList<GDispatcherPort>> dispatchersToTrigger = new HashMap<GTrigger, ArrayList<GDispatcherPort>>();
        //outgoing
        HashMap<Graphical, ArrayList<GInputPort>> outPortToInports = new HashMap<Graphical, ArrayList<GInputPort>>();
        HashMap<GDispatcherPort, ArrayList<GTrigger>> dispatcherToTriggers = new HashMap<GDispatcherPort, ArrayList<GTrigger>>();
        //helper
        HashMap<GInputPort,String> inputValueClass = new HashMap<GInputPort,String>();
        HashMap<Graphical,ArrayList<GOutput>> outBaseToAttach = new HashMap<Graphical,ArrayList<GOutput>>();
        //list of (endpoints of) connections which need to be moved to the internals of the boxnode
        ArrayList<Graphical> addInternal = new ArrayList<Graphical>();//list with connections graphical that need to be moved to the right model

        ArrayList<BasicCommand> commands = new  ArrayList<BasicCommand>();
        for(GConnection con : connections){
            if(GTriggerConnection.class.isAssignableFrom(con.getClass())){
                GTriggerConnection gtc = (GTriggerConnection)con;
                GNode gnt = (GNode)(((RectangleAttachment)gtc.getTrigger().getBase()).getBase());
                GNode gnd = (GNode)(gtc.getDispatcher().getBase());

                if(gn.getGraphicModel().getAllObjects().contains(gnt) && gn.getGraphicModel().getAllObjects().contains(gnd)){
                    //internal connection
                    gn.addInternalObject(con);
                    gmodel.removeObject(con);
                }else if(gn.getGraphicModel().getAllObjects().contains(gnt)){//connection to trigger inside the box
                    ArrayList<GDispatcherPort> dispatchers = dispatchersToTrigger.get(gtc.getTrigger());
                    if(dispatchers == null){
                        dispatchers = new ArrayList<GDispatcherPort>();
                    }
                    dispatchers.add(gtc.getDispatcher());
                    dispatchersToTrigger.put(gtc.getTrigger(), dispatchers);
                    commands.add(new DetachConnectionCommand(con));
                    commands.add(new DeleteCommand(con));
                    //add to the list of the to be corrected models
                    addInternal.add(gtc.getTrigger());
                }else{//connection from a dispatcher inside to a trigger outside the box
                    ArrayList<GTrigger> triggers = dispatcherToTriggers.get(gtc.getDispatcher());
                    if(triggers == null){
                        triggers = new ArrayList<GTrigger>();
                    }
                    triggers.add(gtc.getTrigger());
                    dispatcherToTriggers.put(gtc.getDispatcher(), triggers);
                    commands.add(new DetachConnectionCommand(con));
                    commands.add(new DeleteCommand(con));
                    //add to the list of the to be corrected models
                    addInternal.add(gtc.getDispatcher());
                }
            }else if(GHandleConnection.class.isAssignableFrom(con.getClass())){
                GHandleConnection ghc = (GHandleConnection)con;
                GNode gno = (GNode)(((RectangleAttachment)ghc.getOutput().getBase()).getBase());
                GNode gni = (GNode)(ghc.getInput().getBase());
                
                if(gn.getGraphicModel().getAllObjects().contains(gno) && gn.getGraphicModel().getAllObjects().contains(gni)){
                    //internal connection
                    gn.addInternalObject(con);
                    gmodel.removeObject(con);
                }else if(!gn.getGraphicModel().getAllObjects().contains(gno)){//connection from outport outside to inport inside the box
                    Node nq = sync.get(gno);
                    Port pp = nq.getPort(ghc.getOutput().getBase().getName());
                    OutPort ip = (OutPort) pp;
                    commands.add(new DetachConnectionCommand(con));
                    commands.add(new DeleteCommand(con));
                    ArrayList<GInputPort> gi = new ArrayList<GInputPort>();
                    gi.add(ghc.getInput());
                    ArrayList<GOutput> go = new ArrayList<GOutput>();
                    go.add(ghc.getOutput());
                    gview.notifyObservers(new AddTransmitterCommand(Constants.getValueTransmitterInString(),ghc.getInput().getName(), ip.getValueClass().getName(),gi,go));
                    //add to the list of the to be corrected models
                    addInternal.add(ghc.getInput());
                }else{//connection to inport outside the box
                    Node nq = sync.get(gni);
                    Port pp = nq.getPort(ghc.getInput().getName());
                    InPort ip = (InPort) pp;
                    inputValueClass.put(ghc.getInput(), ip.getValueClass().getName());
                    ArrayList<GInputPort> inports = outPortToInports.get(ghc.getOutput().getBase());
                    if(inports == null){
                        inports = new ArrayList<GInputPort>();
                    }
                    inports.add(ghc.getInput());
                    outPortToInports.put(ghc.getOutput().getBase(), inports);
                    ArrayList<GOutput> outputs = outBaseToAttach.get(ghc.getOutput().getBase());
                    if(outputs == null){
                        outputs = new ArrayList<GOutput>();
                    }
                    outputs.add(ghc.getOutput());
                    outBaseToAttach.put(ghc.getOutput().getBase(),outputs);
                    commands.add(new DetachConnectionCommand(con));
                    commands.add(new DeleteCommand(con));
                    //add to the list of the to be corrected models
                    addInternal.add(ghc.getOutput());
                }
            }
        }

        //now all the hashmaps are filled in. We are prepared to create the transmitters
        for(GTrigger trigger : dispatchersToTrigger.keySet()){
            ArrayList<GDispatcherPort> dispatchers = dispatchersToTrigger.get(trigger);
            ArrayList<GTrigger> tgs = new ArrayList<GTrigger>();
            tgs.add(trigger);
            commands.add(new AddTransmitterCommand(Constants.getTriggerTransmitterInString(),trigger.getName(),dispatchers,tgs));
        }
        for(GDispatcherPort disp : dispatcherToTriggers.keySet()){
            ArrayList<GTrigger> triggers = dispatcherToTriggers.get(disp);
            ArrayList<GDispatcherPort> dps = new  ArrayList<GDispatcherPort>();
            dps.add(disp);
            commands.add(new AddTransmitterCommand(Constants.getTriggerTransmitterOutString(),disp.getName(),dps,triggers));
        }
        for(Graphical outbase : outPortToInports.keySet()){
            ArrayList<GOutput> outs = outBaseToAttach.get(outbase);
            ArrayList<GInputPort> inports = outPortToInports.get(outbase);
            commands.add(new AddTransmitterCommand(Constants.getValueTransmitterOutString(), outbase.getName(),inputValueClass.get(inports.get(0)),inports, outs));
        }

        commands.add(new RedrawCommand(gn));
        gview.notifyObservers(new MacroCommand(commands));

        //move the connections to the right model
        for(Graphical g : gmodel.getObjects(Constants.getConnectionString())){
            if(!gn.getInternalObjects().contains(g)){
                if(GTriggerConnection.class.isAssignableFrom(g.getClass())){
                    GTriggerConnection gt = (GTriggerConnection)g;
                    if(addInternal.contains(gt.getDispatcher()) || addInternal.contains(gt.getTrigger())){
                        gmodel.removeObject(gt);
                        gn.addInternalObject(gt);
                    }
                }else{//GHandleConnection
                    GHandleConnection gh = (GHandleConnection)g;
                    if(addInternal.contains(gh.getInput()) || addInternal.contains(gh.getOutput())){
                        gmodel.removeObject(gh);
                        gn.addInternalObject(gh);
                    }
                }
            }
        }//end of moving
        addToPalette(gn,cb,palette,name, info);
        controller.getDebug().append(LabelReader.getInstance().getString("BoxNodeAddedToPanel"));
    }

    public static void addToPalette(GBoxNode gn, BoxNode cb, Palette palette,String name, String info){
        palette.addPaletteClone(gn, cb);
        TransferableArrayList ba = new TransferableArrayList();

        ba.add(new CloneCommand(gn.getId(), cb.getId()));
        PaletteItem pi = new PaletteItem(ba, name, "custom", info);
        pi.addInfoPanelRef(palette.getInfoPanelRef());
        palette.add(pi);
        palette.updateUI();
   }
}
